﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Helpers.Extensions;
using Helpers;
using HttpRequester.RequestMakers.Base;

namespace HttpRequester
{
    public class RequestChain<TContext>
        where TContext : RequestChainContext, new()
    {
        public virtual TContext Context { get; set; }

        protected Dictionary<Guid, RequestChainItemBase> Chain { get; set; }
        protected Dictionary<Guid, Func<bool>> Aspects { get; set; }
        
        public bool? IsSuccessfulyInvoked { get; set; }
        public string LastContent { get; set; }
        public int LastInvokedIndex { get; set; }

        protected bool ProcessFlag { get; set; }
        public RequestChainContext ParentContext { get; set; }
        public bool WithInheritedContext { get; set; }
        
        public RequestChain()
        {
            Clear();
        }

        public RequestChain(RequestChainContext parentContext)
        {
            ParentContext = parentContext;
            WithInheritedContext = true;

            Clear();
        }


        //public virtual void CopyContext(RequestChainContext parentContext)
        //{
        //    Context.Proxy = parentContext.Proxy;
        //    parentContext.Cookies.Values.ToList().ForEach(cookie => AddCookie(cookie));
        //    parentContext.CommonCustomHeaders.Keys.ToList().ForEach(headerName =>
        //        {
        //            string headerValue = parentContext.CommonCustomHeaders[headerName];
        //            AddCustomHeader(headerName, headerValue);
        //        });
        //    parentContext.CommonWellknownHeaders.Keys.ToList().ForEach(headerName =>
        //        {
        //            string headerValue = parentContext.CommonWellknownHeaders[headerName];
        //            AddWellknownHeaders(headerName, headerValue);
        //        });
        //    LastContent = ParentContext.LastResponseContent;
        //}

        public virtual void Init() { }

        public void Clear()
        {
            Context = new TContext();
            Context.InputCookies = new Dictionary<string, Cookie>();
            Context.CommonCustomHeaders = new Dictionary<string, string>();
            Context.CommonWellknownHeaders = new Dictionary<HttpRequestHeader, string>();

            Chain = new Dictionary<Guid, RequestChainItemBase>();
            Aspects = new Dictionary<Guid, Func<bool>>();
            IsSuccessfulyInvoked = null;
            LastContent = null;
            LastInvokedIndex = -1;

            WithInheritedContext = false;

            Init();
        }

        protected virtual void PrepareRunChain() { }

        public void RunChain()
        {
            PrepareRunChain();

            ProcessFlag = true;
            LastContent = null;
            if (WithInheritedContext)
                LastContent = Context.PreviousResponseContent;
            try
            {
                Chain.Keys.ToList().ForEach(key =>
                    {
                        RequestChainItemBase item = Chain[key];

                        LastInvokedIndex = Chain.Values.ToList().IndexOf(item);

                        Func<bool> aspect = Aspects[key];
                        if (!aspect())
                            return;

                        ProcessFlag = item.IsContextValid();
                        if (!ProcessFlag)
                            throw new ChainInvokationIsFailedException("Context is Not Valid");

                        try
                        {
                            item.PrepareRequest();
                        }
                        catch (Exception ex)
                        {
                            throw new ChainInvokationIsFailedException("Request prepearing was failed", ex);
                        }

                        ProcessFlag = item.IsRequestValid();
                        if (!ProcessFlag)
                            throw new ChainInvokationIsFailedException("Request is Not Valid");

                        try
                        {
                            item.DoRequest();
                        }
                        catch (Exception ex)
                        {
                            throw new ChainInvokationIsFailedException("Request was done with problems", ex);
                        }

                        ProcessFlag = item.IsResponseValid();
                        if (!ProcessFlag)
                            throw new ChainInvokationIsFailedException("Response is Not Valid");

                        try
                        {
                            item.UpdateContext();
                        }
                        catch (Exception ex)
                        {
                            throw new ChainInvokationIsFailedException("Context Update was not done correct", ex);
                        }
                    });
                Context.PreviousResponseContent = LastContent;
                IsSuccessfulyInvoked = true;
            }
            catch (ChainInvokationIsFailedException ex)
            {
                ProcessFlag = false;
                IsSuccessfulyInvoked = false;
            }
        }

        public Guid AddItem<TRequester, TChain, TContext>(RequestChainItemBase<TRequester, TChain, TContext> item)
            where TRequester : RequestManagerPlaneHtml
            where TChain : RequestChain<TContext>
            where TContext : RequestChainContext, new()
        {
            return AddItem(item, null);
        }

        public Guid AddItem<TRequester, TChain, TContext>(RequestChainItemBase<TRequester, TChain, TContext> item, Func<bool> aspect)
            where TRequester : RequestManagerPlaneHtml
            where TChain : RequestChain<TContext>
            where TContext : RequestChainContext, new()
        {
            Guid key = Guid.NewGuid();

            item.Chain = this as TChain;
            
            if(aspect == null)
                aspect = () => true;

            Chain.AddOrReset(key, item);
            Aspects.AddOrReset(key, aspect);

            return key;
        }

        public void AddCustomHeader(string name, string value)
        {
            value = value ?? string.Empty;
            Context.CommonCustomHeaders.AddOrReset(name, value);
        }

        public void AddCustomHeader(string header)
        {
            if (StringHelper.IsnullOrBlank(header))
                return;

            string[] headerParts = header.Split(':');
            if (headerParts.Length > 1)
            {
                string headerName = headerParts[0].Trim();
                string headerValue = headerParts[1].Trim();
                Context.CommonCustomHeaders.AddOrReset(headerName, headerValue);
            }
            else
            {
                Context.CommonCustomHeaders.AddOrReset(header, null);
            }
        }

        public void AddWellknownHeaders(HttpRequestHeader key, string value)
        {
            Context.CommonWellknownHeaders.AddOrReset(key, value);
        }


        public void LoadInputCookies(List<Cookie> source)
        {
            if (Context.InputCookies == null)
                Context.InputCookies = new Dictionary<string, Cookie>();

            if (CollectionHelper.IsNullOrEmpty(source))
                return;

            source.ForEach(c => Context.InputCookies.AddOrReset(c.Name, c));
        }

        public void AddCookie(Cookie c)
        {
            if (c == null) return;
            Context.InputCookies.AddOrReset(c.Name, c);
        }
    }
}
